local prohibitedEntities = { viewmodel = true, predicted_viewmodel = true, physgun_beam = true, worldspawn = true }
local cell_cl_debug = CreateClientConVar( "cell_cl_debug", "0", true, false )

cells.fBrushes = {}

local function BrushAdd( cell )
	if Cells[ tostring(cell) ] and Cells[ tostring(cell) ].BrushesAdded then return end

	if !Cells[ tostring(cell) ] then
		Cells[ tostring(cell) ] = {}
	end
	Cells[ tostring(cell) ].BrushesAdded = true

	for k, v in pairs( cells.fBrushes ) do
		if v:IsValid() and cells.TestForRepeat_P( v:GetCell(), cell, v:GetCellRepeat(), v:GetCellRepeatMin(), v:GetCellRepeatMax() ) then
			Cells[ tostring(cell) ][ v:EntIndex() ] = v
		end
	end
end

local function BrushAddLayer( newCell, layer )
	BrushAdd( newCell + Cell( -1, -1, layer ) )
	BrushAdd( newCell + Cell(  0, -1, layer ) )
	BrushAdd( newCell + Cell(  1, -1, layer ) )

	BrushAdd( newCell + Cell( -1, 0, layer )  )
	BrushAdd( newCell + Cell(  0, 0, layer )  )
	BrushAdd( newCell + Cell(  1, 0, layer )  )

	BrushAdd( newCell + Cell( -1, 1, layer )  )
	BrushAdd( newCell + Cell(  0, 1, layer )  )
	BrushAdd( newCell + Cell(  1, 1, layer )  )
end

local function CellChangeRecieve( msg )
	local ent = msg:ReadEntity()
	local vec = msg:ReadVector()

	local cell = vec.X .. " " .. vec.Y .. " " .. vec.Z
	
	if !IsValid(ent) or table.HasValue(prohibitedEntities, ent:GetClass()) then	return end
	
	if !Cells[cell] then
		Cells[cell] = {}
	end
	
	if ent.OldCell then
		Cells[ent.OldCell][ent:EntIndex()] = nil
	end
	ent.OldCell = cell
	
	Cells[cell][ent:EntIndex()] = ent
	local newCell = cells.VectorToCell( vec )

	BrushAddLayer( newCell, -1 )
	BrushAddLayer( newCell,  0 )
	BrushAddLayer( newCell,  1 )

end
usermessage.Hook("CellChange", CellChangeRecieve)

local function ChangeVisibility( ent, state )
	if state then
		if ent:GetClass() != "class C_RopeKeyframe" then ent:SetNoDraw(false) else ent:SetColor(255,255,255,255) end
		if string.find(ent:GetClass(), "prop_") then ent:CreateShadow()	end
	else
		if ent:GetClass() != "class C_RopeKeyframe" then ent:SetNoDraw(true) else ent:SetColor(255,255,255,0) end
	end
end						

function cells.DrawHUD()
	if cell_cl_debug:GetInt() > 0 then
		draw.RoundedBox( 4, 6, 2, 380, 46, Color(0,0,0,60) )
		draw.SimpleText("Player cell: ".. tostring( LocalPlayer():GetCell() ), "ScoreboardText", 12, 12, Color(255,255,200,200), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
		draw.SimpleText("Player position: " .. tostring( LocalPlayer():GetPos() ), "ScoreboardText", 12, 24, Color(255,255,200,200), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
		draw.SimpleText("Player cell position: " .. tostring( LocalPlayer():GetCellPos() ), "ScoreboardText", 12, 36, Color(255,255,200,200), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
		
		local trace = util.GetPlayerTrace( LocalPlayer() )
		local traceRes = util.TraceLine(trace)
		if traceRes.HitNonWorld then
				draw.SimpleText("Object cell: ".. tostring( traceRes.Entity:GetCell() ), "ScoreboardText", ScrW()/2, (ScrH()/2) + 96, Color(255,255,200,200), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
				draw.SimpleText("Object class: ".. traceRes.Entity:GetClass(), "ScoreboardText", ScrW()/2, (ScrH()/2) + 108, Color(255,255,200,200), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
		end
	end	
end
hook.Add("HUDPaint", "cells.DrawHUD", cells.DrawHUD)

local allowedTypes = { Entity = true, Player = true, Vehicle = true, Weapon = true }

local function RC( cell ) -- RenderCell, named RC for convenience
	local rCell = cell + LocalPlayer():GetCell()
	local entz = Cells[ tostring(rCell) ]

	cam.Start3D( EyePos() - (cell:ToVector() * cells.Size), RenderAngles())
		if entz then
			for k,v in pairs(entz) do
				if allowedTypes[ type(v) ] and v:IsValid() and !prohibitedEntities[v:GetClass()] then
					v:ResetSequence( v:GetSequence() )

					if v.Draw then
						v:Draw()
					elseif v.DrawModel then
						v:DrawModel()
					end
			
					ChangeVisibility( v, true )
				end
			end
		end
	cam.End3D()
end

local function DrawLayer( layer )
	RC( Cell(-1, -1, layer) )
	RC( Cell(0, -1, layer) )
	RC( Cell(1, -1, layer) )

	RC( Cell(-1, 0, layer) )
	RC( Cell(0, 0, layer) )
	RC( Cell(1, 0, layer) )

	RC( Cell(-1, 1, layer) )
	RC( Cell(0, 1, layer) )
	RC( Cell(1, 1, layer) )
end

local cell_cl_rendermode = CreateClientConVar( "cell_cl_rendermode", "2", true, false )

function cells.Render( )
	if cell_cl_rendermode:GetInt() != 0 then
		if cell_cl_rendermode:GetInt() == 1 then
			DrawLayer(  0 )
		else
			DrawLayer( -1 )
			DrawLayer(  0 )
			DrawLayer(  1 )
		end
	end	

	for k,v in pairs( ents.GetAll() ) do
		if !prohibitedEntities[v:GetClass()] then ChangeVisibility( v, false ) end
	end
end
hook.Add("PostDrawOpaqueRenderables", "cells.Render", cells.Render)

hook.Add("InitPostEntity", "cells.RegisterBrushes", function() cells.fBrushes = ents.FindByClass("func_brush") end)

concommand.Add( "cell_printcells", function(ply,cmd,args) PrintTable(Cells) end)